package edu.kit.aifb.ldstream.learner.tasks;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
 

import org.openrdf.model.BNode;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
import org.openrdf.rio.ParserConfig;
import org.openrdf.rio.RDFParser.DatatypeHandling;

import edu.kit.aifb.ldstream.env.Environment;
import edu.kit.aifb.ldstream.learner.dao.Instance;
import edu.kit.aifb.ldstream.learner.dao.Property;
import edu.kit.aifb.ldstream.learner.dao.Type;
import edu.kit.aifb.ldstream.stat.util.IDistributionChecker;

public class PropertyDistributionTask extends LearnerTask{

	private boolean inferencing;
	private Map<Type, ArrayList<Instance>> classMap;
	private Map<Type, HashMap<URI, Property>> resultSet;
	private IDistributionChecker distribChecker;

	public PropertyDistributionTask(Map<Type, ArrayList<Instance>> classMap, 
			Map<Type, HashMap<URI, Property>> resultSet,
			boolean inferencing, IDistributionChecker distribChecker) {
		this.classMap = classMap;
		this.resultSet = resultSet;
		this.inferencing = inferencing;
		this.distribChecker = distribChecker;
	}

	@Override
	public void run() {
		if(Environment.VERBOSE){
			System.out.println("Started to learn the property distributions.");
		}
		startup();
		setPreserveBNodes();
		computePropertyDistribution();
		shutdown();
		if(Environment.VERBOSE){
			System.out.println("Property distributions learned.");
		}
	}

	private void computePropertyDistribution(){
		// the number of all properties is needed for the weight computation
		int count = 0;
		int size = 0;

		int tenth = 0;
		int j = 1;
		double percentage = 0;
		if(Environment.LOGGING){
			tenth = Math.max(classMap.size() / 10, 1);
			percentage = 100d / classMap.size();
		}
		for(Type t : this.classMap.keySet()){

			if(Environment.LOGGING){
				if((j % tenth) == 0 ){
					Environment.log.info("PropertyDistribution leaned to: " + Math.round(j * percentage) +"%");
				}
				j++;
			}
			// only crawl classes with instances
			if(!this.classMap.get(t).isEmpty()){

				Map<URI, ArrayList<Integer>> propCountMap = new LinkedHashMap<URI, ArrayList<Integer>>();
				this.resultSet.put(t, new HashMap<URI, Property>());
				for(Instance instance : this.classMap.get(t)){

					RepositoryResult<Statement> result = null;
					Statement s = null;
					URI pred, objURI = null;
					Value obj;
					boolean interdomain;
					try {
						result = this.con.getStatements(instance.getURI(), null, null, this.inferencing);
						try{
							while(result.hasNext()){
								s = result.next();
								pred = s.getPredicate();
								obj =  s.getObject();
								if(!propCountMap.containsKey(pred)){
									boolean bnode = false;
									interdomain = false;
									if(obj instanceof BNode){
										bnode = true;
									}else if (obj instanceof URI){
										objURI = (URI) s.getObject();
										// it's an interdomain property if the namespace of the object differs from the 
										// property namespace
										interdomain = ! t.getURI().getNamespace().equals(objURI.getNamespace());
									}
									propCountMap.put(pred, new ArrayList<Integer>());
									propCountMap.get(pred).add(Integer.valueOf(0));

									this.resultSet.get(t).put(new URIImpl(pred.stringValue()), 
											new Property(pred.stringValue(), bnode, interdomain));

								}
								size = propCountMap.get(pred).size();
								count = propCountMap.get(pred).get(size - 1);
								propCountMap.get(pred).set(size - 1, count + 1);

								if(obj instanceof BNode){
									computePropertyDistribution((BNode)obj, propCountMap, t);
								}
							}

						}finally{
							result.close();
						}
					} catch (RepositoryException e) {
						e.printStackTrace();
					}

					// the instance is crawled add a new occurrence value to the list for the next instance
					for(URI uri : propCountMap.keySet()){
						if(instance.getOccurrence() > 0){
							size = propCountMap.get(uri).size();
							count = propCountMap.get(uri).get(size - 1);
							//	System.out.println(instance.toString() + " property " + uri.stringValue() + " count: " + count + " instance occures: " + instance.getOccurrence());
							propCountMap.get(uri).set(size - 1, (int) (Math.round((double)count / (double)instance.getOccurrence())));
							//	System.out.println(instance.toString() + " property " + uri.stringValue() + " adjusted count: " + (int) (Math.round((double)count / (double)instance.getOccurrence())));
						}
						// only add new occurrence if the number of instances is not already reached
						if(propCountMap.get(uri).size() < this.classMap.get(t).size()){
							propCountMap.get(uri).add(Integer.valueOf(0));
						}
					}

				}
				// every instance is crawled
				int i;
				for(URI uri : propCountMap.keySet()){
					// creating an array for the distribution check, it needs the length of numInstances
					// because occurrence of 0 needs to be regarded too
					Integer[] values = new Integer[this.classMap.get(t).size()];
					i = 0;
					for(Integer oc : propCountMap.get(uri)){
						values[i++] = oc;
					}
					for(; i < values.length; i++){
						values[i] = Integer.valueOf(0);
					}
					this.resultSet.get(t).get(uri).setDistribution(this.distribChecker.discrete(values, false));
				}
			}
		}
	}

	private void computePropertyDistribution(BNode b, Map<URI, ArrayList<Integer>> propCountMap, Type t){

		RepositoryResult<Statement> result = null;
		Statement s = null;
		URI pred, objURI = null;
		Value obj;
		int count;
		int size;
		boolean interdomain;
		try {
			result = this.con.getStatements(b, null, null, this.inferencing);
			try{
				while(result.hasNext()){
					s = result.next();
					pred = s.getPredicate();
					obj = s.getObject();
					if(!propCountMap.containsKey(pred)){
						boolean bnode = false;
						interdomain = false;
						if(obj instanceof BNode){
							bnode = true;
						}else if (obj instanceof URI){
							objURI = (URI) obj;
							interdomain = ! t.getURI().getNamespace().equals(objURI.getNamespace());
						}
						propCountMap.put(new Property(((URI) pred).stringValue(), bnode, interdomain), new ArrayList<Integer>());
						propCountMap.get(pred).add(Integer.valueOf(0));
						this.resultSet.get(t).put(new URIImpl(pred.stringValue()), 
								new Property(pred.stringValue(), bnode, interdomain));
					}
					size = propCountMap.get(pred).size();
					count = propCountMap.get(pred).get(size - 1);
					propCountMap.get(pred).set(size - 1, count + 1);
					if(obj instanceof BNode){
						computePropertyDistribution((BNode)obj, propCountMap, t);
					}
				}
			}finally{
				result.close();
			}
		} catch (RepositoryException e) {
			e.printStackTrace();
		}
	}


	private void setPreserveBNodes(){
		this.con.setParserConfig(new ParserConfig(true, true, true, DatatypeHandling.VERIFY));
	}
}
